import { serialize } from 'class-transformer';
import * as fs from 'fs';
const request = require('request');
const requestp = require('request-promise');
const crypto = require('crypto'); //加载加密文件
import { Builder, Parser } from 'xml2js';
import { getRandomInt } from '../../util/utiles';
import * as NP from 'number-precision';

export class WX {
  private readonly mch_id: string;

  private readonly key: string;

  private cerkPem: string;

  private keyPem: string;

  constructor(mch_id: string, key: string) {
    this.mch_id = mch_id;
    this.key = key;
    this.cerkPem = fs.readFileSync('src/authority/apiclient_cert.pem', 'utf-8');
    this.keyPem = fs.readFileSync('src/authority/apiclient_key.pem', 'utf-8');
  }

  /**
   * @description: 获取access_token
   * @param {string} accessTokenKey redis储存key
   * @return:
   */
  public async setAccessToken(
    appid: string,
    secret: string,
    accessTokenKey: string,
  ): Promise<any> {
    const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
    return new Promise(async (resolv, reject) => {
      //先从redis读取
      // this.SessionKey.redisKey = accessTokenKey;
      // let accessToken:string = await this.SessionKey.currLoadString();
      // if(accessToken)
      //     resolv(accessToken);
      // //读取失败重写
      let accessToken: string = '';
      request(url, (err, res, body) => {
        accessToken = JSON.parse(body).access_token;
        // this.SessionKey.setRedisAccessToken(accessTokenKey,accessToken);
        resolv(accessToken);
      });
    });
  }

  /**
   * @description: 获取用户授权手机号
   * @param {string} code
   * @param {string} accessToken
   * @return:
   */
  public async getUserphonenumber(code: string, accessToken: string) {
    const url = `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${accessToken}`;
    return new Promise(async (resolv, reject) => {
      let requestData = {
        method: 'POST',
        uri: url,
        encoding: null,
        body: {
          code: code,
        },
        json: true,
      };
      //通过微信接口获取小程序码
      requestp(requestData).then(function (body) {
        resolv(body); //返回图片流
      });
    });
  }

  /**
   * @description: 生成二维码
   * @param {string} accessToken
   * @param {string} path 路径
   * @param {map} optionJson 参数map
   * @return:
   */
  protected async qrcode(
    path: string,
    accessToken: string,
    qrcodeurl: string,
    optionJson: Map<string, string | number | object>,
  ): Promise<any> {
    return new Promise(async (resolve, reject) => {
      qrcodeurl = qrcodeurl + '?access_token=' + accessToken;
      let optionParam: string = '?';
      for (let [k, v] of optionJson) {
        optionParam += `${k}=${v}&`;
      }
      let requestData = {
        method: 'POST',
        uri: qrcodeurl,
        encoding: null,
        body: {
          path: `${path}${optionParam}`, //带参数的path
          width: 800,
          is_hyaline: false,
        },
        json: true,
      };
      //通过微信接口获取小程序码
      requestp(requestData).then(function (body) {
        var base64Img = body.toString('base64'); // base64图片编码字符串
        var dataBuffer = Buffer.from(base64Img, 'base64');
        resolve(dataBuffer); //返回图片流
      });
    });
  }

  /**
   * @description: 小程序支付统一下单
   * @param {string} body 商品描述
   * @param {string|number} out_trade_no 商户订单号
   * @param {number} total_fee 标价金额
   * @param {string} spbill_create_ip 终端IP
   * @param {string} notify_url 通知地址
   * @param {string} openid
   * @return:
   */
  protected async appWxPay(
    appid: string,
    body: string,
    out_trade_no: string | number,
    total_fee: number,
    spbill_create_ip: string,
    notify_url: string,
    openid: string,
  ): Promise<string> {
    const nonce_str = getRandomInt(1, 9999);
    const key = this.key;
    total_fee = NP.times(total_fee, 100);
    //获取签名
    let json = {
      appid: appid,
      mch_id: this.mch_id,
      openid: openid,
      nonce_str: nonce_str,
      body: body,
      out_trade_no: out_trade_no,
      total_fee,
      spbill_create_ip: spbill_create_ip,
      notify_url: notify_url,
      trade_type: 'JSAPI',
    };
    console.log(json);
    const sign: string = await this.paysignjsapi(json);
    json['sign'] = sign.toUpperCase();
    var jsonBuilder = new Builder();
    var xmlParser = new Parser({ explicitArray: false, ignoreAttrs: true });
    var json2xml = await jsonBuilder.buildObject(json);
    let requestData = {
      method: 'POST',
      uri: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
      encoding: null,
      body: json2xml,
    };
    //通过微信接口获取小程序码
    let das: any;
    await requestp(requestData).then(function (res) {
      xmlParser.parseString(res, async (err, result) => {
        var md5 = crypto.createHash('md5');
        das = result['xml'];
        console.log(das);
        let _package = 'prepay_id=' + das.prepay_id;
        let nowTime = new Date();
        let timeStamp = Math.round(nowTime.getTime() / 1000);
        let string =
          'appId=' +
          das.appid +
          '&nonceStr=' +
          das.nonce_str +
          '&package=' +
          _package +
          '&signType=MD5&timeStamp=' +
          timeStamp.toString();
        string = string + '&key=' + key;
        md5.update(string);
        string = await md5.digest('hex');
        let packages = {
          paySign: string.toUpperCase(),
          timeStamp: timeStamp,
        };
        das = Object.assign(das, packages);
      });
    });
    return das;
  }

  /** 企业付款到零钱
   *
   *
   * @param {string} appId
   * @param {string} partner_trade_no
   * @param {string} openid
   * @param {number} amount
   * @param {string} desc
   * @returns
   * @memberof WX
   */
  public async appTransfers(
    appId: string,
    partner_trade_no: string,
    openid: string,
    amount: number,
    desc: string,
  ): Promise<object> {
    const nonce_str = getRandomInt(1, 9999);
    const key = this.key;
    amount = NP.times(amount, 100);
    let json = {
      mch_appid: appId,
      mchid: this.mch_id,
      nonce_str: nonce_str,
      partner_trade_no: partner_trade_no,
      amount: amount,
      openid,
      check_name: 'NO_CHECK',
      desc,
    };
    const sign: string = await this.paysignjsapi(json);
    json['sign'] = sign.toUpperCase();
    var jsonBuilder = new Builder();
    var xmlParser = new Parser({ explicitArray: false, ignoreAttrs: true });
    var json2xml = await jsonBuilder.buildObject(json);
    let requestData = {
      method: 'POST',
      uri: 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers',
      encoding: null,
      body: json2xml,
      agentOptions: {
        cert: this.cerkPem,
        key: this.keyPem,
      },
    };
    //通过微信接口获取小程序码
    let das: any;
    await requestp(requestData).then(function (res) {
      xmlParser.parseString(res, async (err, result) => {
        das = result['xml'];
      });
    });
    return das;
  }

  /**
   * @description: 小程序统一退款
   * @param {string|number} out_trade_no 商户订单号
   * @param {string|number} out_refund_no 退款单号
   * @param {number} total_fee 订单金额
   * @param {number} refund_fee 退款金额
   * @param {string} notify_url 退款结果通知url
   * @param {string} refund_desc 退款原因
   * @return:
   */
  protected async refund(
    appid: string,
    out_trade_no: string,
    out_refund_no: string,
    total_fee: number,
    refund_fee: number,
    notify_url: string,
    refund_desc?: string,
  ): Promise<object> {
    const nonce_str = getRandomInt(1, 9999);
    const key = this.key;
    total_fee = NP.times(total_fee, 100);
    refund_fee = refund_fee * 100;
    let json = {
      appid: appid,
      mch_id: this.mch_id,
      nonce_str: nonce_str,
      out_trade_no: out_trade_no,
      total_fee: total_fee,
      refund_fee: refund_fee,
      out_refund_no: out_refund_no,
    };
    const sign: string = await this.paysignjsapi(json);
    json['sign'] = sign.toUpperCase();
    var jsonBuilder = new Builder();
    var xmlParser = new Parser({ explicitArray: false, ignoreAttrs: true });
    var json2xml = await jsonBuilder.buildObject(json);
    let requestData = {
      method: 'POST',
      uri: 'https://api.mch.weixin.qq.com/secapi/pay/refund',
      encoding: null,
      body: json2xml,
      agentOptions: {
        cert: this.cerkPem,
        key: this.keyPem,
      },
    };
    //通过微信接口获取小程序码
    let das: any;
    await requestp(requestData).then(function (res) {
      xmlParser.parseString(res, async (err, result) => {
        das = result['xml'];
      });
    });
    return das;
  }

  /**
   * @description: 生成支付签名
   * @param {string} body 商品描述
   * @param {string} nonce_str 随机字符串
   * @param {string|number} out_trade_no 商户订单号
   * @param {number} total_fee 标价金额
   * @param {string} spbill_create_ip 终端IP
   * @param {string} notify_url 通知地址
   * @param {string} openid
   * @return:
   */
  protected async paysignjsapi(ret: object): Promise<string> {
    var md5 = crypto.createHash('md5');
    var string = await this.raw(ret);
    string = `${string}&key=${this.key}`;
    md5.update(string);
    string = await md5.digest('hex');
    return string;
  }

  /**
   * @description: 签名排序
   * @param {object} args 签名object
   * @return:
   */
  protected async raw(args: object): Promise<string> {
    var keys = Object.keys(args);
    keys = keys.sort();
    var newArgs = {};
    keys.forEach(function (key) {
      newArgs[key.toLowerCase()] = args[key];
    });
    var string = '';
    for (var k in newArgs) {
      string += '&' + k + '=' + newArgs[k];
    }
    string = string.substr(1);
    return string;
  }

  /**
   * @description: 发送模板消息
   * @param {string} access_token
   * @param {string} openid
   * @param {string} template_id 模板id
   * @param {string} page 页面路径
   * @param {string} form_id
   * @param {string} data 参数内容
   * @param {string} emphasis_keyword 放大的关键词
   * @return:
   */
  protected async templateMessage(
    access_token: string,
    openid: string,
    template_id: string,
    page: string,
    form_id: string,
    data: Map<string, object>,
    emphasis_keyword?: string,
  ): Promise<boolean> {
    let result: boolean = true;
    let dataJson = {};
    for (let [k, v] of data) {
      dataJson[k] = v;
    }
    //请求data内容
    let json = {
      touser: openid,
      template_id,
      page,
      form_id,
      emphasis_keyword,
      data: dataJson,
    };
    //请求参数
    let requestData = {
      method: 'post',
      uri: `https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=${access_token}`,
      qs: json,
      body: json,
      data: json,
      headers: {
        'User-Agent': 'Request-Promise',
      },
      json: true,
    };
    const resultData = await requestp(requestData);
    //如果失败返回false
    if (resultData && resultData.errcode !== 0) {
      result = false;
    }
    return result;
  }

  /**
   * @description: 公众号网页授权获取openid
   * @param {string} code
   * @param {string} appid
   * @param {string} secret
   * @return:
   */
  public async getPublicOpenid(
    code: string,
    appid: string,
    secret: string,
  ): Promise<string> {
    //请求参数
    let requestData = {
      method: 'get',
      uri: `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`,
      data: '',
      headers: {
        'User-Agent': 'Request-Promise',
      },
      json: true,
    };
    const resultData = await requestp(requestData);
    return resultData.openid;
  }

  /**
   * @description: 公众号发送模板消息
   * @param {string} openid
   * @param {string} access_token
   * @param {Date} template_id 模板id
   * @param {string} appid 跳转小程序appid
   * @param {Mpa<string,any>} data 参数
   * @return:
   */
  public async sendPublicTemplate(
    access_token: string,
    openid: string,
    template_id: string,
    appid: string,
    data: Map<string, any>,
  ): Promise<boolean> {
    let result: boolean = true;
    let dataJson = {};
    for (let [k, v] of data) {
      dataJson[k] = v;
    }
    //请求data内容
    let json = {
      touser: openid,
      template_id,
      miniprogram: {
        appid,
      },
      data: dataJson,
    };
    //请求参数
    let requestData = {
      method: 'post',
      uri: `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${access_token}`,
      qs: json,
      body: json,
      data: json,
      headers: {
        'User-Agent': 'Request-Promise',
      },
      json: true,
    };
    const resultData = await requestp(requestData);
    //如果失败返回false
    if (resultData && resultData.errcode !== 0) {
      result = false;
    }
    return result;
  }
}
